package com.spring.theory.boot;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.List;
import java.util.Map;

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.context.annotation.Import;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ClassUtils;

/**
 * TestBootApplication
 *
 * @author H
 * @version 1.0
 * @date 2024-05-14 15:09
 */
@Slf4j
public class TestBootApplication {

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();

        /**
         * 设置不允许bean定义重写
         * springboot容器默认不允许bean定义重写（会被覆盖）
         */
        context.setAllowBeanDefinitionOverriding(false);
        context.registerBean("BootConfig", BootConfig.class);
        context.registerBean(ConfigurationClassPostProcessor.class);
        context.refresh();

        for (String name : context.getBeanDefinitionNames()) {
            log.info("name==>{}", name);
        }
        context.getBean(BootBean.class);

    }

    @Configuration
    //导入第三方配置类；bean容器管理
    //@Import({AutoConfiguration1.class,AutoConfiguration2.class})
    @Import({TestImportSelector.class})
    static class BootConfig {

        @Bean
        public BootBean bootBean() {
            log.info("本项目bootBean");
            return new BootBean("本项目");
        }

    }

    /**
     * DeferredImportSelector会延迟bean加载的时机
     */
    static class TestImportSelector implements DeferredImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            // 加载META-INF/spring.factories文件
            List<String> names = SpringFactoriesLoader.loadFactoryNames(TestImportSelector.class, null);
            return names.toArray(new String[0]);
        }
    }

    /**
     * 模拟第三方配置类
     */
    @Configuration
    @ConditionalOnClass(className = "org.mybatis.spring.SqlSessionTemplate", exist = true)
    static class AutoConfiguration1 {
        @Bean
        //当不存在该bean时才创建
        @ConditionalOnMissingBean
        public BootBean bootBean() {
            log.info("第三方bootBean1");
            return new BootBean("第三方");
        }
    }

    @Configuration
    @ConditionalOnClass(className = "org.mybatis.spring.SqlSessionTemplate", exist = false)
    static class AutoConfiguration2 {
        @Bean
        public BootBean2 bootBean2() {
            log.info("第三方bootBean2");
            return new BootBean2();
        }
    }

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Conditional(TestConditionalOnClass.class)
    @interface ConditionalOnClass {

        String className() default "";

        boolean exist() default false;

    }

    static class TestConditionalOnClass implements Condition {
        @Override
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnClass.class.getName());
            String className = attributes.get("className").toString();
            boolean exist = (boolean) attributes.get("exist");
            boolean present = ClassUtils.isPresent(className, null);
            return exist ? present : !present;
        }
    }

    @ToString
    @NoArgsConstructor
    @AllArgsConstructor
    static class BootBean {
        private String name;
    }

    @ToString
    @NoArgsConstructor
    @AllArgsConstructor
    static class BootBean2 {
        private String name;
    }


}
